﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace MonoStrategy
{

    /// <summary>
    /// A resource stack is a integral part of the whole economy. Its a simple but powerful
    /// concept with which most resource tasks seen in Settlers can be archieved.
    /// </summary>
    public class GenericResourceStack : PositionTracker
    {
        /// <summary>
        /// There is a maximum number of items supported for ALL resource stacks.
        /// </summary>
        public const Int32 DEFAULT_STACK_SIZE = 8;

        private List<Movable> m_Stack = new List<Movable>();
        private Int32 m_LastCount = 0;
        private Int32 m_MaxCount = 0;

        internal bool HasPriority { get { return PriorityNode != null; } }
        internal LinkedListNode<GenericResourceStack> PriorityNode { get; set; }
        /// <summary>
        /// The building, if any, this stack is attached to.
        /// </summary>
        internal BaseBuilding Building { get; private set; }
        /// <summary>
        /// Kind of this stack.
        /// </summary>
        public ResStackType Type { get; private set; }
        /// <summary>
        /// Resource typed stored here.
        /// </summary>
        public Resource Resource { get; private set; }
        /// <summary>
        /// Is raised whenever resources are removed or added.
        /// </summary>
        public event DChangeHandler<GenericResourceStack, Int32> OnCountChanged;
        /// <summary>
        /// Is raised whenever <see cref="MaxCount"/> changes.
        /// </summary>
        public event DChangeHandler<GenericResourceStack, Int32> OnMaxCountChanged;
        /// <summary>
        /// For each instance you can set the maximum stack size between
        /// one and <see cref="DEFAULT_STACK_SIZE"/>.
        /// </summary>
        public Int32 MaxCount
        { 
            get { return m_MaxCount; }
            internal set
            {
                int backup = m_MaxCount;

                if ((value < 0) || (value > DEFAULT_STACK_SIZE))
                    throw new ArgumentOutOfRangeException();

                m_MaxCount = value;

                if (Type == ResStackType.Provider)
                {
                    while (Available > MaxCount)
                    {
                        RemoveResource();
                    }
                }
                else // if (Type == ResStackType.Query)
                {
                    while (VirtualCount > MaxCount)
                    {
                        if (Requested > 0) // remove jobs first
                        {
                            var movable = m_Stack.Where(e => e != null).First();

                            RemoveInternal(movable);

                            movable.Stop();
                        }
                        else
                            RemoveResource();
                    }

                }

                if (OnMaxCountChanged != null)
                    OnMaxCountChanged(this, backup, m_MaxCount);
            }
        }
        /// <summary>
        /// The virutal resource count is calculated differently for queries and
        /// providers. For queries it is the sum of <see cref="Available"/> and
        /// <see cref="Requested"/>, for providers its the difference. 
        /// </summary>
        /// <remarks>
        /// Further a query is considered to be full if this value has reached the maximum
        /// stack size, even if for now not all resources are actually added to the
        /// stack, but on their way. A provider can also be empty even if there is
        /// the maximum possible amount of resources stored.
        /// </remarks>
        public Int32 VirtualCount
        {
            get
            {
                if (Type == ResStackType.Query)
                    return Available + Requested;
                else
                    return Available - Requested;
            }
        }
        /// <summary>
        /// The amount of resources building this stack. This does NOT include resource
        /// jobs that will decrease or increase the stack count in near future.
        /// </summary>
        public Int32 Available
        {
            get
            {
                int c = 0;

                foreach (var e in m_Stack)
                {
                    if (e == null)
                        c++;
                }

                return c;
            }
        }
        /// <summary>
        /// The amount of resource job regarding this stack. This does NOT include resources
        /// already placed on the stack.
        /// </summary>
        public Int32 Requested
        {
            get
            {
                int c = 0;

                foreach (var e in m_Stack)
                {
                    if (e != null)
                        c++;
                }

                return c;
            }
        }
        internal bool IsRemoved { get; private set; }
        internal Int64 MinProviderID { get; set; }

        internal void MarkAsRemoved()
        {
            if (IsRemoved)
                throw new InvalidOperationException();

            IsRemoved = true;

            foreach (var movable in m_Stack.ToArray())
            {
                if ((movable == null) || (movable.Job == null))
                    continue;

                movable.Job = null;
                movable.Stop();
            }
        }

        internal GenericResourceStack(ResStackType inType, Resource inResource)
            : this(inType, inResource, DEFAULT_STACK_SIZE)
        {
        }

        internal GenericResourceStack(ResStackType inType, Resource inResource, Int32 inMaxStack) : this(null, inType, inResource, inMaxStack)
        {
        }

        internal GenericResourceStack(BaseBuilding inBuilding, ResStackType inType, Resource inResource, Int32 inMaxStack)
        {
            if ((inMaxStack < 1) || (inMaxStack > DEFAULT_STACK_SIZE))
                throw new ArgumentOutOfRangeException();

            MaxCount = inMaxStack;
            Type = inType;
            Resource = inResource;
            Building = inBuilding;
        }

        internal bool HasSpace
        {
            get
            {
                if(Type == ResStackType.Provider)
                    return Available < MaxCount;
                else
                    return VirtualCount < MaxCount;
            }
        }

        private void Update()
        {
            if (m_LastCount == VirtualCount)
                return;

            try
            {
                if (OnCountChanged != null)
                    OnCountChanged(this, m_LastCount, VirtualCount);
            }
            finally
            {
                m_LastCount = VirtualCount;
            }
        }

        private bool CheckRange(int inCount)
        {
            if ((inCount < 0) || (inCount > MaxCount))
                return false;

            return true;
        }

        /// <summary>
        /// Adds a resource, depending on stack type, directly, meaning it will increase
        /// <see cref="Available"/>.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Adding a resource would overflow the stack.</exception>
        internal void AddResource()
        {
            AddInternal(null);
        }

        /// <summary>
        /// Adds a resource job, depending on stack type, meaning it will increase
        /// <see cref="Requested"/>.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Adding another resource would overflow the stack.</exception>
        internal void AddJob(Movable inMovable)
        {
            if (inMovable == null)
                throw new ArgumentNullException();

            AddInternal(inMovable);
        }

        private void AddInternal(Movable inMovable)
        {
            Debug.Assert(CheckRange(VirtualCount) && CheckRange(Available) && CheckRange(Requested));

            m_Stack.Insert(0, inMovable);

            if (!CheckRange(VirtualCount) || !CheckRange(Available) || !CheckRange(Requested))
            {
                m_Stack.RemoveAt(0);

                throw new ArgumentOutOfRangeException();
            }

            Update();
        }

        /// <summary>
        /// Adds a resource, depending on stack type, directly, meaning it will increase
        /// <see cref="Available"/>.
        /// </summary>
        /// <exception cref="KeyNotFoundException">No resource available to remove.</exception>
        internal void RemoveResource()
        {
            RemoveInternal(null);
        }

        /// <summary>
        /// Removes a resource job meaning it will decrease <see cref="Requested"/>.
        /// </summary>
        /// <exception cref="KeyNotFoundException">Given movable was not found.</exception>
        internal void RemoveJob(Movable inMovable)
        {
            if (inMovable == null)
                throw new ArgumentNullException();

            RemoveInternal(inMovable);
        }

        private void RemoveInternal(Movable inMovable)
        {
            bool success = false;

            for (int i = 0; i < m_Stack.Count; i++)
            {
                if (m_Stack[i] == inMovable)
                {
                    m_Stack.RemoveAt(i);

                    success = true;
                    break;
                }
            }

            if (!success)
                throw new KeyNotFoundException();

            Update();
        }
    }
}
